// UCLA CS 111 Lab 1 command reading

#include "command.h"
#include "command-internals.h"
#include "alloc.h"

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>

typedef int (*getByteFunction) (void *);

command_t make_simple_cmd(command_stream_t simpleStream);
command_t make_subshell_cmd(command_stream_t subStream);

typedef struct command_stream{
	char *text;
	int text_length;
	int linenum;
	command_stream_t next;
	command_stream_t prev;
}command_stream;

typedef struct command command;

void init_cmd_stream(command_stream *stream){
	stream->text = (char*)checked_malloc(2*sizeof(char));
	stream->text_length = 0;
	stream->linenum = 0;
	stream->next = NULL;
	stream->prev = NULL;
}

int isword(char c){
	return isalpha(c) || isdigit(c) || (c == '!') || (c == '%') || (c == '+') || (c == ',')
			|| (c == '-') || (c == '.') || (c == '/') || (c == ':') || (c == '@') || (c == '^')
			|| (c == '_');
}
int isspecial(char c){
	return (c == '&') || (c == '|') || (c == ';') || (c == '(') || (c == ')') || (c == '<') || (c == '>') || (c == '#');
}

int checkspecial(char *c, int *i, int max){
	char ch = *(c+*i);
	if(ch == '&'){
		if(!(*i+1<max))
			return 0;
		if(*i+1<max && *(c+*i+1)!= '&')
			return 0;
		if(*i+2<max && *(c+*i+2) == '&')
			return 0;
		*i+= 1;
		return 1;
	}
	if(ch == '|'){
		if(*i+1<max && *(c+*i+1) == '|'){
			if(*i+2<max && *(c+*i+2) == '|')
				return 0;
			*i+= 1;
		}
		return 1;
	}
	if(ch!= ';' && ch!= '(' && ch!= ')' && ch!= '<' && ch!= '>' && ch!= '#')
		return 0;
	return 1;
}

command_stream *new_stream(){
	  command_stream *temp = (command_stream*)checked_malloc(sizeof(command_stream));
	  init_cmd_stream(temp);
	  return temp;
}

void add_stream(command_stream_t *stream_c, command_stream_t *stream, int *t_len){
	//add stream to the end of c
	// null terminate word
	(*stream)->text[(*stream)->text_length] = 0;
	//add stream to current node's next
	(*stream_c)->next = *stream;
	//set last pointer to stream
	*stream_c = *stream;
	*stream = new_stream();
	//give stream pointer to last node on stack
	(*stream)->prev = *stream_c;
}

char** extract_command(command_stream_t *s){
	command_stream_t token;
	char **word = (char**)checked_malloc(2*sizeof(char*));
	unsigned int word_len;
	unsigned int word_pos;
	word_len = 2;
	word_pos = 0;
	size_t resize_len;
	token = *s;

	// Remove beginning whitespace
	while(*(token->text) == ' ' || *(token->text) == '\t' || *(token->text) == '\n'){
		token = token->next;
	}

	// Build array of regular tokens in word
	while(isword(*(token->text)) || *(token->text) == ' ' || *(token->text) == '\t'){
		if(word_pos  == word_len){
			word_len *= 2;
			resize_len = word_len*sizeof(char*);
			word = checked_grow_alloc(word, &resize_len);
		}
		word[word_pos] = checked_malloc(token->text_length * sizeof(char));
		word[word_pos] = memcpy(word[word_pos], token->text, token->text_length+1);
		(word_pos)+= 1;
		token = token->next;
		while(*(token->text) == ' ' || *(token->text) == '\t'){
			token = token->next;
		}
	}


	// If token array is null, exit due to unexpected token
	if(*word == NULL)
		error(1, 0, "%i: Invalid command.", token->linenum);

	// If token array contains only a newline, exit due to unexpected token
	if(**word == '\n' && word_pos == 1)
		error(1, 0, "%i: Invalid command.", token->linenum);

	// Remove trailing whitespace
	while(*(word[(word_pos)-1]) == ' ' || *(word[(word_pos)-1]) == '\t'){

		if(word_pos == 0)
			error(1, 0, "%i: Command consisted entirely of whitespace.", token->linenum);
		(word_pos)--;
	}
	word[word_pos] = NULL;

	if(*(token->text) != '\n')
		token = token->prev;
	*s = token;
	return word;
}

command_stream_t make_command_stream (int (*get_next_byte) (void *), void *get_next_byte_argument){
	command_stream_t p_stream = checked_malloc(sizeof(command_stream));
	init_cmd_stream(p_stream);
	command_stream_t p_last_stream = checked_malloc(sizeof(command_stream));
	init_cmd_stream(p_last_stream);
	command_stream_t return_stream = p_last_stream;
	int stream_t_len = 1;
	int text_len = 2;
	int text_pos = 0;

	//int inword = 0;
	int linenum = 1;
	size_t resize_len;
	char ch = get_next_byte(get_next_byte_argument);
	while(ch != EOF){
		if(isword(ch)){
		  while(isword(ch)){
			  //inword = 1;
			  if(text_pos  == text_len){
				  text_len *= 2;
				  resize_len = text_len*sizeof(char);
				  p_stream->text = checked_grow_alloc(p_stream->text, &resize_len);
			  }
			  p_stream->text[text_pos] = ch;
			  text_pos+= 1;
			  ch = get_next_byte(get_next_byte_argument);
		  }
		  p_stream->linenum = linenum;
		  p_stream->text_length = text_pos;
		  p_stream->text[text_pos] = 0;

		  if(p_stream->text[0] != 0){
			  add_stream(&p_last_stream, &p_stream, &stream_t_len);
		  }
		  text_len = 2;
		  text_pos = 0;
		}
		if(isspecial(ch)){
			while(isspecial(ch)){
			  //inword = 1;
			  if(text_pos  == text_len){
				  text_len *= 2;
				  text_len = text_len*sizeof(char);
				  p_stream->text = checked_grow_alloc(p_stream->text, &resize_len);
			  }
			  p_stream->text[text_pos] = ch;
			  text_pos+= 1;
			  char c = ch;
			  ch = get_next_byte(get_next_byte_argument);
			  if(c!= '&' && c!= '|')
				  break;
			  if(ch!= c)
				  break;
		  }
		  p_stream->linenum = linenum;
		  p_stream->text_length = text_pos;
		  p_stream->text[text_pos] = 0;
		  if(p_stream->text[0] != 0){
			  add_stream(&p_last_stream, &p_stream, &stream_t_len);
		  }
		  text_len = 2;
		  text_pos = 0;
	  }
	  if(!isword(ch) && !isspecial(ch) && ch  != ' ' && ch  != '\n' && ch  != '\t')
		  error(1, 0, "%i: syntax error.", linenum);

	  if(ch  == ' ' || ch  == '\n' || ch  == '\t'){
		 // inword = 1;
		  p_stream->text[0] = ch;
		  p_stream->linenum = linenum;
		  p_stream->text_length = 1;
		  p_stream->text[1] = 0;
		  add_stream(&p_last_stream, &p_stream, &stream_t_len);
		  text_len = 2;
		  text_pos = 0;
		  if(ch == '\n'){
			  linenum+= 1;
		  }
		  ch = get_next_byte(get_next_byte_argument);
	  }
	 // inword = 0;
	}

	return_stream = return_stream->next;
	return_stream->prev = NULL;
	p_last_stream->next = NULL;

  return return_stream;
}

command_t make_cmd_tree(command_stream_t s){

	command_stream_t token = s;
	command_t command_left;
	command_t command_temp;
	command_t command_head = (command_t)checked_malloc(sizeof(command));

	while(token != NULL && token->text != NULL){

		//iterate through all spaces until we hit something meaningful
		if(*(token->text) == ' ' || *(token->text) == '\t'){
			while(*((token->next)->text) == ' ' || *((token->next)->text) == '\t'){
				token = token->next;
			}
		}

		//go to next line if line is a comment
		if(*(token->text) == '#'){
			if(token->prev != NULL && *((token->prev)->text) != ' ' && *((token->prev)->text) != '\t' && *((token->prev)->text) != '\n'){
				error(1, 0, "%i: syntax error.", token->linenum);
			}
			else{
				while(*(token->text) != '\n')
					token = token->next;
				token = token->next;
				continue;
			}
		}

		// Create simple command node
		if(isword(*(token->text))){
			command_head = make_simple_cmd(token);
		}

		//create PIPE node
		if(token->text[0] == '|' && token->text[1] == 0){
			if(command_head->type != SIMPLE_COMMAND && command_head->type != SUBSHELL_COMMAND && command_head->u.command[0] == NULL)
				error(1, 0, "%i: Error. Invalid command before |.", token->linenum);

				command_left = command_head;
				command_head = (command_t)checked_malloc(sizeof(command));
				command_head->type = PIPE_COMMAND;
				command_head->status = -1;
				command_head->input = NULL;
				command_head->output = NULL;
				token = token->next;
				while(*(token->text) == ' ' || *(token->text) == '\t' || *(token->text) == '\n'){
					token = token->next;
				}
				if(token->text[0] == '('){
					command_head->u.command[1] = make_subshell_cmd(token);
				}
				else{
					command_head->u.command[1] = make_simple_cmd(token);
				}
				//check for AND or OR which are higher precedence, and insert pipe into their command[1]
				if(command_left->type == AND_COMMAND || command_left->type == OR_COMMAND)
				{
					command_head->u.command[0] = command_left->u.command[1];
					command_left->u.command[1] = command_head;
					command_head = command_left;
				}
				else
				{
					command_head->u.command[0] = command_left;

				}
		}

		//create an AND node
		if(token->text[0] == '&' && token->text[1] == '&'){
			if(command_head->type != SIMPLE_COMMAND && command_head->type != SUBSHELL_COMMAND && command_head->u.command[0] == NULL)
				error(1, 0, "%i: Error. Invalid command before &&.", token->linenum);
			else if (command_head->type == AND_COMMAND)
			{

			}
			command_left = command_head;
			command_head = (command_t)checked_malloc(sizeof(command));
			command_head->type = AND_COMMAND;
			command_head->status = -1;
			command_head->input = NULL;
			command_head->output = NULL;
			command_head->u.command[0] = command_left;

			token = token->next;
			while(*(token->text) == ' ' || *(token->text) == '\t' || *(token->text) == '\n'){
				token = token->next;
			}
			if(token->text[0] == '('){
				command_head->u.command[1] = make_subshell_cmd(token);
			}
			else{
				command_head->u.command[1] = make_simple_cmd(token);
			}
		}

		//create OR node
		if(token->text[0] == '|' && token->text[1] == '|'){
			if(command_head->type != SIMPLE_COMMAND && command_head->type != SUBSHELL_COMMAND && command_head->u.command[0] == NULL)
				error(1, 0, "%i: Error. Invalid command before ||.", token->linenum);
			command_left = command_head;

			command_head = (command_t)checked_malloc(sizeof(command));
			command_head->type = OR_COMMAND;
			command_head->status = -1;
			command_head->input = NULL;
			command_head->output = NULL;
			command_head->u.command[0] = command_left;

			token = token->next;
			while(*(token->text) == ' ' || *(token->text) == '\t' || *(token->text) == '\n'){
				token = token->next;
			}
			if(token->text[0] == '('){
				command_head->u.command[1] = make_subshell_cmd(token);
			}
			else{
				command_head->u.command[1] = make_simple_cmd(token);
			}
		}

		if(token->text[0] == '<'){
			token = token->next;
			if(token->text[0] == '\n')
				error(1, 0, "%i: Syntax error. No input file provided.", token->linenum);
			command_head->input = *(extract_command(&token));
		}
		if(token->text[0] == '>'){
			token = token->next;
			if(token->text[0] == '\n')
				error(1, 0, "%i: Syntax error. No output file provided.", token->linenum);
			command_head->output = *(extract_command(&token));
		}
		if(token->text[0] == '('){
			command_head = make_subshell_cmd(token);
		}
		if(token->text[0] == ')'){
			error(1, 0, "%i: Syntax error. Extra \')\'.", token->linenum);
		}

		if(token->text[0] == ';' || token->text[0] == '\n'){
			if(token->next != NULL)
			{
				token = token->next;

				while(*(token->text) == '\n'){
				token = token->next;
				}
			}
			break;
		}

		if(token->next != NULL)
			token = token->next;
		else
			break;
	}
	*s = *token;
	return command_head;
}
command_t make_subshell_cmd(command_stream_t subStream){
	command_t subshell = (command_t)checked_malloc(sizeof(command));
	command_stream_t token;
	token = subStream;
	token = token->next;
	subshell->type = SUBSHELL_COMMAND;
	subshell->status = -1;
	subshell->input = NULL;
	subshell->output = NULL;
	subshell->u.subshell_command = make_cmd_tree(token);
	if(*(token->next->text) != ')'){
		error(1, 0, "%i: Syntax error. Expected \')\'.", token->linenum);
	}
	token = token->next;
	*subStream = *token;
	return subshell;
}

command_t make_simple_cmd(command_stream_t simpleStream)
{
	command_t simple = (command_t)checked_malloc(sizeof(command));
	command_stream_t token;
	token = simpleStream;
	char **word;

	simple->type = SIMPLE_COMMAND;
	simple->status = -1;
	simple->input = NULL;
	simple->output = NULL;
	word = extract_command(&token);
	simple->u.word = word;
	*simpleStream = *token;
	return simple;
}

command_t read_command_stream (command_stream_t s)
{
	//  error (1, 0, "command reading not yet implemented");

  /* dereference s to access command_stream elements to read bytes until space or eof
   * call this a word and check for validity of bash command
   * return command_t that holds command*/
	if(s->next == NULL)
		return 0;
	command_t command_head;
	command_head = make_cmd_tree(s);

  return command_head;
}
